/*
 * Copyright (c) 2011, Harald Westphal
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * * Neither the name of the copyright holder nor the
 * names of his contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package com.googlecode.jrcp;

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.net.bsd.RCommandClient;

/**
 * RCP client for reading or writing single remote files.
 * 
 * @author Harald Westphal <h.westphal@gmx.de>
 */
public final class RemoteFile {

	private static final int BUFFER_SIZE = 1024;

	private final String host;
	private final String user;
	private final String file;

	/**
	 * Creates a remote file representation.
	 * 
	 * @param host
	 *            remote host name
	 * @param user
	 *            remote user name
	 * @param file
	 *            remote file name
	 */
	public RemoteFile(String host, String user, String file) {
		this.host = host;
		this.user = user;
		this.file = file;
	}

	/**
	 * Copies the remote file's data to the given output stream. The output
	 * stream has to be closed by the caller.
	 * 
	 * @param fos
	 *            data sink
	 * @throws IOException
	 */
	public void copyTo(final OutputStream fos) throws IOException {
		final RCommandClient rcmdClient = new RCommandClient();
		rcmdClient.connect(host);
		try {
			rcmdClient.rcommand(System.getProperty("user.name"), user,
					"rcp -f " + file);
			final InputStream is = rcmdClient.getInputStream();
			final OutputStream os = rcmdClient.getOutputStream();
			sendAck(os);
			// read server response header
			final ByteArrayOutputStream bos = new ByteArrayOutputStream();
			for (;;) {
				final int read = is.read();
				if (read < 0) {
					throw new EOFException("unexpected end of stream");
				}
				if (read == '\n') {
					break;
				}
				bos.write(read);
			}
			final String serverResponse = bos.toString("UTF-8");
			// expected header for regular file: C0644 <file-size> <filename>
			if (!serverResponse.isEmpty() && serverResponse.charAt(0) == 'C') {
				int start = 0;
				int end = serverResponse.indexOf(" ", start + 1);
				start = end + 1;
				end = serverResponse.indexOf(" ", start + 1);
				final long filesize = Long.parseLong(serverResponse.substring(
						start, end));
				sendAck(os);
				copyStream(is, fos, filesize);
				expectAck(is);
				sendAck(os);
			} else {
				throw new FileNotFoundException("unexpected server response: "
						+ serverResponse.trim());
			}
		} finally {
			rcmdClient.disconnect();
		}
	}

	/**
	 * Copies the data read from the given input stream to the remote file. The
	 * input stream has to be closed by the caller.
	 * 
	 * @param fis
	 *            data source
	 * @param filesize
	 *            number of bytes to be read from the input stream
	 * @throws IOException
	 */
	public void copyFrom(final InputStream fis, final long filesize)
			throws IOException {
		final RCommandClient rcmdClient = new RCommandClient();
		rcmdClient.connect(host);
		try {
			rcmdClient.rcommand(System.getProperty("user.name"), user,
					"rcp -t " + file);
			final InputStream is = rcmdClient.getInputStream();
			final OutputStream os = rcmdClient.getOutputStream();
			expectAck(is);
			final String header = "C0644 " + filesize + " "
					+ new File(file).getName() + "\n";
			os.write(header.getBytes("UTF-8"));
			expectAck(is);
			copyStream(fis, os, filesize);
			sendAck(os);
			expectAck(is);
		} finally {
			rcmdClient.disconnect();
		}
	}

	private void sendAck(final OutputStream os) throws IOException {
		os.write(0);
		os.flush();
	}

	private void expectAck(final InputStream is) throws IOException {
		if (is.read() != 0) {
			throw new IOException("server did not send ACK");
		}
	}

	private void copyStream(final InputStream is, final OutputStream os,
			long filesize) throws IOException {
		final byte[] buf = new byte[BUFFER_SIZE];
		for (;;) {
			final int length = is.read(buf, 0,
					BUFFER_SIZE < filesize ? BUFFER_SIZE : (int) filesize);
			if (length < 0) {
				throw new EOFException("unexpected end of stream");
			}
			os.write(buf, 0, length);
			filesize -= length;
			if (filesize == 0) {
				break;
			}
		}

	}

}
